﻿/* ----------------------------------------------------------------------------
 * Copyright (c) Philip Stears 
 *
 * This source code is subject to terms and conditions of the Microsoft Public 
 * License (Ms-PL).
 * 
 * By using this source code in any fashion, you are agreeing to be bound 
 * by the terms of the Microsoft Public License.
 * 
 * To request a copy of the Microsoft Public License, send a request by email
 * to license@connectbasic.net.
 *
 * This notice must not be removed from this software.
 * ---------------------------------------------------------------------------- */

namespace ConnectBasic.Execution
{
    using System;
    using System.Collections.Generic;
    using Microsoft.Scripting;
    using Microsoft.Scripting.Runtime;

    internal class BasicTokenSink : ITokenSink
    {
        private static readonly HashSet<string> BuiltInDataTypes = GetBuiltIns();
        private List<TokenInfo> mTokens = new List<TokenInfo>();

        #region .cctor

        static HashSet<string> GetBuiltIns()
        {
            var result = new HashSet<string>(StringComparer.OrdinalIgnoreCase);

            result.Add("Single");
            result.Add("Float");
            result.Add("Double");
            result.Add("Boolean");
            result.Add("Byte");
            result.Add("Integer");
            result.Add("Long");
            result.Add("String");
            result.Add("Currency");
            result.Add("Variant");
            result.Add("Object");
            result.Add("DateTime");

            return result;
        }

        #endregion

        #region Public Methods

        public TokenInfo[] GetTokens()
        {
            return mTokens.ToArray();
        }

        #endregion

        #region ITokenSink Members

        void ITokenSink.ReceiveToken(BasicToken tokenInfo)
        {
            mTokens.Add(TokenToTokenInfo(tokenInfo));
        }
        private static TokenInfo TokenToTokenInfo(BasicToken token)
        {
            // If the token info is an identifier which correspond to a built-in
            // data type, change its category to be a keyword
            var ti = new TokenInfo(token.Span, token.Category, TokenTriggers.None);

            if (token.Category == TokenCategory.Identifier && BuiltInDataTypes.Contains(token.ValueString))
            {
                ti.Category = TokenCategory.Keyword;
            }

            return ti;
        }

        #endregion
    }


    /// <summary>
    /// Supports token categorization.
    /// </summary>
    /// <remarks>
    /// <para>
    /// The token categorizer uses the parser to guide
    /// the tokenization process.
    /// </para>
    /// </remarks>
    internal class BasicTokenizerService : TokenizerService
    {
        private TokenInfo[] mTokens = { };
        private int mCursor = -1;
        private ErrorSink mErrors = ErrorSink.Null;

        internal BasicTokenizerService()
        {
        }

        public override Microsoft.Scripting.SourceLocation CurrentPosition
        {
            get
            {
                return mCursor < 0 || mCursor == mTokens.Length ? SourceLocation.None : mTokens[mCursor].SourceSpan.Start;
            }
        }

        public override object CurrentState
        {
            get { return null; }
        }

        public override Microsoft.Scripting.ErrorSink ErrorSink
        {
            get
            {
                return mErrors;
            }
            set
            {
                mErrors = value;
            }
        }

        public override void Initialize(object state, System.IO.TextReader sourceReader, Microsoft.Scripting.SourceUnit sourceUnit, Microsoft.Scripting.SourceLocation initialLocation)
        {

            // Create a token sink and use it to run a parse so that 
            // we can get the tokens that the parser walked over
            var sink = new BasicTokenSink();
            BasicParser.ParseStandardModule(sourceUnit, mErrors, sink);

            // Store the tokens received by the sink
            mTokens = sink.GetTokens();
        }

        public override bool IsRestartable
        {
            get { return true; }
        }

        public override Microsoft.Scripting.TokenInfo ReadToken()
        {
            if (mCursor == mTokens.Length)
            {
                return new TokenInfo(SourceSpan.None, TokenCategory.EndOfStream, TokenTriggers.None);
            }

            return mTokens[++mCursor];
        }
    }
}
